﻿using System.IO.Ports;

namespace Digitalmes.Infrastructure.Common.SerialPorts;

/// <summary>
/// 串口管理对象。
/// </summary>
internal sealed class SerialPortsManager : IDisposable
{
    private readonly string _defaultConfigFilePath = Path.Combine(AppContext.BaseDirectory, "Assets", "Config", "serialPorts.json");
    private readonly List<GallerySerialPort> _serialPorts = [];

    private readonly IPublisher _publisher;
    private readonly IOptions<SerialPortsConfig> _options;
    private readonly ILogger _logger;

    public SerialPortsManager(IPublisher publisher, IOptions<SerialPortsConfig> options, ILogger<SerialPortsManager> logger)
    {
        _publisher = publisher;
        _options = options;
        _logger = logger;

        Reload();
    }

    /// <summary>
    /// 获取默认的串口（第一个）。
    /// </summary>
    /// <returns></returns>
    public IGallerySerialPort? GetSerialPort()
        => _serialPorts.FirstOrDefault();

    /// <summary>
    /// 获取唯一的串口。
    /// </summary>
    /// <param name="portName">串口名称</param>
    /// <returns></returns>
    public IGallerySerialPort? GetSerialPort(string portName) 
        => _serialPorts.SingleOrDefault(s => s.PortName == portName);

    /// <summary>
    /// 通过目的名称获取唯一的串口。
    /// </summary>
    /// <param name="purpose">目的名称</param>
    /// <returns></returns>
    public IGallerySerialPort? GetSerialPortViaPurpose(string purpose)
        => _serialPorts.SingleOrDefault(s => s.Purpose == purpose);

    /// <summary>
    /// 重新从配置文件中注册串口。
    /// </summary>
    private void Reload()
    {
        var configFilePath = _options.Value.ConfigFilePath ?? _defaultConfigFilePath;
        if (!Path.Exists(configFilePath))
        {
            return;
        }

        var context = File.ReadAllText(configFilePath);

        var options = context.FromJson<List<SerialPortOptions>>();
        if (options == null)
        {
            return;
        }

        foreach (var option in options)
        {
            SerialPort serialPort = new(option.PortName, option.BaudRate, option.Parity, option.DataBits, option.StopBits)
            {
                Encoding = Encoding.ASCII,
            };
            if (option.ReadBufferSize is not null)
            {
                serialPort.ReadBufferSize = option.ReadBufferSize.Value;
            }
            if (option.WriteBufferSize is not null)
            {
                serialPort.WriteBufferSize = option.WriteBufferSize.Value;
            }
            if (option.ReceivedBytesThreshold is not null)
            {
                serialPort.ReceivedBytesThreshold = option.ReceivedBytesThreshold.Value;
            }
            if (option.ReadTimeout is not null)
            {
                serialPort.ReadTimeout = option.ReadTimeout.Value;
            }
            if (option.WriteTimeout is not null)
            {
                serialPort.WriteTimeout = option.WriteTimeout.Value;
            }

            serialPort.DataReceived += async (object sender, SerialDataReceivedEventArgs e) =>
            {
                var serialPort = (SerialPort)sender;
                var content = serialPort.ReadExisting();

                if (!string.IsNullOrEmpty(content))
                {
                    await _publisher.Publish(new SerialDataReceivedEvent(new GallerySerialPort(serialPort), content));
                }
            };
            serialPort.ErrorReceived += async (object sender, SerialErrorReceivedEventArgs e) =>
            {
                var serialPort = (SerialPort)sender;
                await _publisher.Publish(new SerialErrorReceivedEvent(new GallerySerialPort(serialPort), e.EventType));
            };

            var serialPort2 = new GallerySerialPort(serialPort)
            {
                Purpose = option.Purpose,
            };
            _serialPorts.Add(serialPort2);

            _logger.LogInformation("已注册串口 {PortName}", option.PortName);

            if (option.AllowAutoOpen)
            {
                // 开启串口
                try
                {
                    serialPort2.Open();
                    _logger.LogInformation("已打开串口 {PortName}", option.PortName);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning("串口 {PortName} 开启异常，原因：{Message}", option.PortName, ex.Message);
                }
            }
        }
    }

    public void Dispose()
    {
        _serialPorts.ForEach(serialPort => serialPort.Dispose());
        _serialPorts.Clear();
    }
}
